package main

import "fmt"

func main() {
	/*
		前言：
		Go语言中没有类的概念，也不支持 “类” 的继承等面向对象的概念。
		Go语言中通过结构体的内嵌再配合 95_接口 比面向对象具有更高的扩展性和灵活性。
	*/

	/*
		    9_结构体：
		    Go语言中数组可以存储同一类型的数据，但是在结构体中我们可以为不同项定义不同的数据类型。
		    结构体是由一系列具有相同 或者 不同的数据构成的数据集合。
		    Go语言中的基础数据类型可以表示一些事物的基本属性，但是当我们想表达一个事物的全部或者部分属性是，
		    这时候再用单一的基本数据类型就明显无法满足需求了，Go语言提供了一种数据类型，可以封装 多个基本数据类型，
		    这种数据类型叫做结构体，也就是我们可以用结构体定义自己的类型了，

			Go语言中有两种聚合数据类型，一个是数组，一个是结构体。
	*/

	//Go语言中通过结构来实现面向对象。    结构体是类型的数据结构

	/*
		定义结构体：
		结构体定需要使用type 和 struct关键字
		struct 语句定义一个新的结构体类型，结构体中有一个或者多个成员。
		type 语句设定了该结构体类型的名称。
		结构体格式如下：
			type 结构体类型名 struct {
				字段名1 字段类型1
				字段名2 字段类型2
				....
			}
		其中：
			1.类型名 ，表示自定义结构体的名称，在同一个包内不能重复。
			2.字段名：表示结构体字段名。结构体中的字段名必须唯一。
				结构体字段的可见性：结构体中字段大写开头的表示可公开访问的，
				小写的表示私有（仅在定义当前结构体的包中可访问）。
			3.字段类型：表示结构体字段的具体类型。
	*/
	type person struct {
		name string
		city string
		age  int
	}
	//也可以向下面这样写
	//type person struct{
	//	name,city string
	//	age int
	//}

	/*
			1.结构体默认初始化
			结构体本身也是一种类型（我们自己定义的类型），我们可以像声明内置类型一样使用var 关键字声明结构体类型。
			var关键字声明结构体类型
		    var 结构体实例 结构体类型
	*/
	var jack person
	fmt.Printf("jack:%#v\n", jack)

	/**
	Tips:
		%#v\n是一个格式化动词，表示以Go 语法格式打印值，对于结构体会显示字段名，对于字符串会显示引号，会尽量显示完整的信息。
	*/
	/*
			2.使用值或键值对结构体初始化。
			声明+初始化
		    方法一：使用值初始化：
				结构体实例:=结构体类型 {value1,value2,...valuen}
				（1.必须初始化所有字段。（如果少值会报错；too few values in 结构体类型{...}）
		          2.初始化值的填充顺序必须与字段在结构体中的声明顺序一致。
		          3.该方式不能和键值初始化方式混用）
			方法二：使用键值对初始化
				结构体实例:={member1:value1,member2:value2,...,membern:membern}
				可以不初始化结构体的所有字段。当某些字段没有初始值的时候，该字段可以不写。此时，没有指定初始值的字段的值就是该字段类型的零值。
			方法三：对结构体指针进行键值对初始化
				结构体实例的指针:=&结构体类型{member1:value1,member2:value2,...,membern:membern}
				可以不初始化结构体的所有字段。当某些字段没有初始值的时候，该字段可以不写。此时，没有指定初始值的字段的值就是该字段类型的零值。
	*/
	tom := person{"tom", "杭州", 20}
	fmt.Printf("tom:%#v\n", tom)
	marry := person{name: "marry", city: "杭州", age: 21}
	fmt.Printf("marry:%#v\n", marry)
	mike := &person{name: "mike", city: "杭州", age: 24}
	fmt.Printf("mike:%#v\n", mike)
	/*
		使用访问成员的方式初始化结构体
		我们通过 . 来访问结构体的字段（成员变量）
	*/
	jack.name = "Jack"
	jack.city = "杭州"
	jack.age = 18
	fmt.Println("jack:", jack)
	/*
		访问结构体成员
		1.如果要访问结构体成员，需要使用点号 . 操作符 格式为 ： 9_结构体.成员名
		2.Go语言也支持在结构体指针上使用 . 点号操作符来访问结构体成员。  ： *&9_结构体.成员名  (&9_结构体.成员名 获取的到是地址)
	*/
	fmt.Println(marry.name)
	fmt.Println(*&mike.name)

	/*
		匿名结构体与结构体的匿名字段
	*/
	//匿名结构体（在定义一些临时数据结构等场景下还可以使用匿名结构体。）
	var user struct {
		Name string
		Age  int
	}
	user.Name = "Jack"
	user.Age = 18
	fmt.Printf("user:%#v\n", user) //user:struct { Name string; Age int }{Name:"Jack", Age:18}

	/*
		结构体的匿名字段
		结构体允许其成员字段在声明时候 没有字段名而只有类型，这种没有名字的字段就称为匿名字段。
		匿名字段默认采用类型名作为字段名，结构体要求字段名称必须唯一，
		因此一个结构体中，同种类型的匿名字段只能有一个。
	*/
	type student struct {
		string
		int
	}
	s1 := student{"heihei", 90}
	fmt.Printf("s1:%#v\n", s1)
	fmt.Println(s1.string)
	fmt.Println(s1.int)
	/*
		结构体指针
		1.使用new 实例化结构体，返回指向结构体的指针
			我们可以通过使用 new 关键字对结构体进行实例化，得到的是 结构体的地址。格式如下：
	*/
	var p1 = new(person)
	fmt.Printf("%T\n", p1)     //*main.person
	fmt.Printf("p1:%#v\n", p1) //p1:&main.person{name:"", city:"", age:0}
	//需要注意的是在Go语言中支持对结构体指针直接使用 . 来访问结构体的成员。
	p1.name = "p1" //在底层其实是*p1.name="p1"   这是Go语言帮助我们实现的语法糖。
	p1.city = "杭州"
	p1.age = 20
	fmt.Printf("p1:%#v\n", p1)
	/*
		2.取结构体的地址实例化
			使用 & 对结构体变量进行取地址操作相当于对该结构体类型进行一次new 实例化操作。
	*/
	var p2 = &person{}
	fmt.Printf("%T\n", p2)     //*main.person
	fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
	p2.name = "博客"
	p2.age = 30
	p2.city = "成都"
	fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"博客", city:"成都", age:30}
	/*
		1.你可以声明 指向结构体的指针 类似于 其他指针变量，该指针变量可以存储结构体变量的地址
		然后通过将 & 符号置于结构体变量前来查看结构体变量地址并赋值给指针。
			//声明
			var struct_pointer *结构体名
			//初始化
			struct_pointer =&结构体实例
			//声明+初始化
			struct_pointer := &结构体名{这里可以初始化值}
		2.使用指针访问成员：
			使用结构体指针访问成员： 使用结构体 指针访问结构体成员，使用 “.” 操作符
			struct_pointer.title
		   Go语言底层帮助我们实现了(*struct_pointer).title
	*/

	/*
		结构体作为函数参数
		1.实例1，将结构体作为函数参数
		2.示例2，将结构体指针作为函数参数
	*/

	/*
		结构体内存布局   Go结构体占用内存是连续的。
	*/

	/*
			构造函数
		结构体是一种特殊的函数，用来在对象实例化的时候初始化对象的成员变量。
		Go语言中没有构造函数我们可以自己实现。
		例如：下面的代码就实现了一个person 的构造函数。因为 struct 是值类型，
		如果结构体比较复杂的话，值拷贝性能开销会比较大，所以该构造函数返回的是结构体指针类型
	*/
	p3 := newPerson("p3", "杭州", 20)
	fmt.Printf("p3:%#v\n", p3)
	fmt.Println(p3.sayHello()) //结构体上的方法
	/*
		嵌套结构体：
		一个结构体可以嵌套包含另外一个结构体或者结构体指针
	*/
	user1 := User{
		Name:   "pprof",
		Gender: "女",
		Address: Address{
			Provide: "黑龙江",
			City:    "哈尔滨",
		},
	}
	fmt.Printf("user1:%#v\n", user1)
	fmt.Println(user1.Gender)       //访问外层结构体成员
	fmt.Println(user1.Address.City) //访问内层结构体成员

	/**
	方法和接收器：
	指针和非指针接收器的使用
	在计算机中，小对象由于值复制时的速度比较快，所以是个使用非指针接收器。
	大对象因为复制性能比较低，是个使用指针接收器，在接收器和参数传递时不进行复制，值传递指针。

	什么时候使用指针类型接收器？？
	1.需要修改接受者的值
	2.接受者是拷贝代价比较大等待对象
	3.保证一致性，如果有某个方法使用了指针接收者，那么其他的方法也应该使用指针接收器。

	*/

	//嵌套匿名结构体：
	user2 := User2{
		Name:    "pprof",
		Gender:  "女",
		Address: Address{Provide: "122", City: "444"},
	}
	fmt.Printf("user1:%#v\n", user2)
	fmt.Println(user2.Gender)       //访问外层结构体成员
	fmt.Println(user2.Address.City) //访问内层结构体成员
	fmt.Println(user2.City)         //访问内层结构体成员(可以直接访问匿名结构体内的成员)
	//当访问结构体成员的时候会先去结构体中查找该字段，找不到就去匿名结构体中查找。

	//嵌套结构体的字段名冲突
	var user3 User3
	user3.Name = "pprof"
	user3.Gender = "男"
	user3.Account = "22222"
	user3.Provide = "黑龙江"
	//user3.City="xxx"  //这行代码会报错的
	//在遇到嵌套结构体字段名冲突的时候我们访问同名字段的时候必须指明结构体的名称。如下：
	user3.Address.City = "杭州"
	user3.Email.City = "台州"
	//结构体的继承：
	d1 := &Dog{
		Feet: 4,
		Animal: &Animal{ //注意嵌套的是结构体指针
			name: "乐乐",
		},
	}
	d1.wang()
	d1.move() //继承到的Animal中的方法

}

type Address struct {
	Provide string
	City    string
}
type Email struct {
	Account string
	City    string
}
type User struct {
	Name    string
	Gender  string
	Address Address
}
type User2 struct {
	Name   string
	Gender string
	Address
}
type User3 struct {
	Name   string
	Gender string
	Address
	Email
}

type person1 struct {
	name string
	city string
	age  int
}

func (p *person1) sayHello() string {
	return "hello"
}

//结构体的继承

// 动物结构体
type Animal struct {
	name string
}

// 动物结构体的move方法
func (a *Animal) move() {
	fmt.Printf("%s会动！\n", a.name)
}

// 狗结构体
type Dog struct {
	Feet    int8
	*Animal //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
	fmt.Printf("%s会汪汪汪~\n", d.name)
}

/*
*
自己定义的结构体构造函数
*/
func newPerson(name, city string, age int) *person1 {
	return &person1{
		name: name,
		city: city,
		age:  age,
	}
}
